import EventTarget from './EventTarget'
import {num, abs, objectFit} from './utils'
import {RoundRectPath, Style} from './interface'
import PointIn from './PointIn'

interface MediaFrame {
    rate: number
    frames: number
}

export default class Box extends EventTarget {
    // 这些属性变化时需要重新计算图形路径
    static PATH_CHANGE:string[] = ['x', 'y', 'width', 'height', 'originX', 'originY', 'borderWidth', 'radius']
    // 由谁构建的实例
    readonly name:string = 'box'
    // 图形形状名称
    shape: string = ''
    // 样式
    style: Style = {
        x: 0,
        y: 0,
        width: 0,
        height: 0,
        borderWidth: 0,
        borderColor: '',
        radius: 0,
        shadowX: 0,
        shadowY: 0,
        shadowBlur: 0,
        shadowColor: '',
        rotate: 0,
        scaleX: 1,
        scaleY: 1,
        opacity: 1,
        background: '',
        overflow: 'hidden',
        originX: 0,
        originY: 0,
        font: "12px",
        color: "#333333",
        align: 'left',
        mode: 'contain'
    }
    // 子节点
    children: Box[] = []
    // 用作背景的媒体, video或image
    media: any = null
    // 媒体绘制的区域信息
    mediaRect: any = null
    // 父节点
    parent: Box = null
    // 文本
    text: string
    mediaFrame: MediaFrame|null = null
    _mediaFrameTime: number = 0
    _rendTime: number = Date.now()
    _rendDelta: number = 0
    // 当影响图形重新计算的属性改变时，以此为true，之后在渲染时会统一计算一次
    _pathDirty: boolean = false
    // 当图形变化或媒体变化时，重新计算媒体的布局
    _mediaDirty: boolean = false
    // 图形的外圈路径信息
    _out: number[]
    // 图形的内圈路径信息（只有带边框时才有值）
    _in: number[]
    constructor(style?:Style){
        super()
        for(let prop of Box.PATH_CHANGE){
            let val = this.style[prop]
            Object.defineProperty(this.style, prop, {
                set: newVal => {
                    val = Box.styleFormat(prop, newVal)
                    this._pathDirty = true
                    this._mediaDirty = true
                },
                get: () => val
            })
        }
        let mode = this.style.mode
        Object.defineProperty(this.style, 'mode', {
            set: newVal => {
                mode = newVal
                this._mediaDirty = true
            },
            get: () => mode
        })

        let media = this.media
        Object.defineProperty(this, 'media', {
            set: newVal => {
                media = newVal || null
                if(!media){
                    this.mediaRect = null
                }else{
                    this._mediaDirty = true
                    Box.computeMedia(this)
                }
            },
            get: () => media
        })
        Object.assign(this.style, style)
    }
    // 格式化某些样式
    static styleFormat(key:string, val:any): any{
        switch(key){
            case 'borderWidth':
                return (!val || val < 0) ? 0 : val
            case 'radius':
                if(typeof val === 'number') {
                    return abs(val)
                }else {
                    let res = []
                    let has = false
                    for(let i=0; i<4; i++){
                        res[i] = abs(val[i])
                        if(res[i]) has = true
                    }
                    if(!has) return 0
                    if(res[0] === res[1] && res[1] === res[2] && res[2] === res[3]) return res[0]
                    return res
                }
            case 'scale':
                if(typeof val === 'number') {
                    val = num(val, 1)
                    return [val, val]
                }else{
                    return [num(val[0], 1), num(val[1], 1)]
                }
            default: return val
        }
    }
    // 计算媒体路径
    static computeMedia(box: Box): void{
        let media = box.media
        if(!media) return
        let mediaWidth = Math.round(media.width || media.videoWidth)
        let mediaHeight = Math.round(media.height || media.videoHeight)
        if(!mediaWidth || !mediaHeight) return
        box._mediaDirty = false
        let {mode, width, height, mediaMaxWidth, mediaMaxHeight} = box.style
        let opt = {
            mediaWidth,
            mediaHeight,
            boxWidth: width,
            boxHeight: height,
            mode,
            maxWidth: mediaMaxWidth,
            maxHeight: mediaMaxHeight
        }
        let res = objectFit(opt)
        box.mediaRect = res
    }
    // text
    static drawText(ctx: CanvasRenderingContext2D, box: Box): void{
        if(!box.text) return
        const s = box.style
        ctx.save()
        ctx.translate(-s.originX, -s.originY)
        if(s.color) ctx.fillStyle = s.color
        if(s.font) ctx.font = s.font
        let x = 0
        ctx.textBaseline = 'top'
        if(s.align === 'center'){
            x = s.width / 2
            ctx.textAlign = 'center'
        }
        ctx.fillText(box.text, x, 0)
        ctx.restore()
    }
    // 添加子节点
    public add(box:Box){
        if(!box) return
        if(box.parent) box.remove()
        let zIndex = Number(box.style.zIndex) || 0
        box.style.zIndex = zIndex
        box.parent = this
        this.children.push(box)
        this.children.sort((a:Box, b:Box) => {
            return a.style.zIndex - b.style.zIndex
        })
    }
    // 移除节点
    public remove(){
        let parent = this.parent
        if(!parent) return
        let i = parent.children.indexOf(this)
        this.parent.children.splice(i, 1)
        this.parent = null
    }
    // 绘图
    public render(ctx:CanvasRenderingContext2D): void{
        if(!ctx) return
        let now = Date.now()
        let delta = now - this._rendTime
        if(delta < 200) this._rendDelta = delta
        this._rendTime = now
        if(this._pathDirty) this.computePath()
        let s = this.style
        let cx = s.x+s.originX
        let cy = s.y+s.originY
        ctx.save()
        ctx.translate(cx, cy)
        //opacity
        ctx.globalAlpha = s.opacity
        //rotate
        ctx.rotate(s.rotate * Math.PI/180)
        //scale
        ctx.scale(s.scaleX, s.scaleY)
        //shadow
        if (!((s.shadowX == 0 && s.shadowY === 0 && s.shadowBlur === 0) || !s.shadowColor)) {
            ctx.shadowColor = s.shadowColor
            ctx.shadowOffsetX = s.shadowX
            ctx.shadowOffsetY = s.shadowY
            ctx.shadowBlur = s.shadowBlur
		}
        // draw outer
        ctx.beginPath()
        this.draw(ctx, this._out)
        ctx.fillStyle = s.background || 'transparent'
        ctx.fill()
        // overflow
        if(s.overflow === 'hidden') ctx.clip()
        // draw media
        if(this._mediaDirty) Box.computeMedia(this)
        if(this.media && this.mediaRect){
            let {mediaOriginWidth: ow, mediaOriginHeight: oh, mediaX: cutX, mediaY: cutY, mediaWidth: cutW, mediaHeight: cutH} = this.mediaRect
            let mediaFrame = this.mediaFrame
            if(!mediaFrame || !mediaFrame.frames || !mediaFrame.rate){
                ctx.drawImage(this.media, 0, 0, ow, oh, cutX - s.originX, cutY - s.originY, cutW, cutH)
            }else{
                let sw = ow/mediaFrame.frames
                let n = Math.round(this._mediaFrameTime / (1000/mediaFrame.rate))
                if(n >= mediaFrame.frames) {
                    this._mediaFrameTime = 0
                    n = 0
                }
                ctx.drawImage(this.media, n*sw, 0, sw, oh, cutX - s.originX, cutY - s.originY, s.width, cutH)
                this._mediaFrameTime += this._rendDelta
            }
        }

        // draw text
        Box.drawText(ctx, this)

        // draw children
        ctx.save()
        ctx.translate(-s.originX, -s.originY)
        for(let child of this.children){
            if(child.render) child.render(ctx)
        }
        ctx.restore()

        // draw border
        if(s.borderWidth > 0 && s.borderColor){
            ctx.beginPath()
            this.draw(ctx, this._out)
            this.draw(ctx, this._in)
            ctx.fillStyle = s.borderColor
            ctx.fill('evenodd')
        }
        ctx.fillStyle = 'red'
        ctx.fillRect(-2, -2, 4, 4)
        // ctx.fillRect(this._hit[0]-2, this._hit[1]-2, 4, 4)
        ctx.restore()
    }
    // 继承者重写方法：根据不同图形计算它们的路径
    public computePath(){}
    // 继承者重写方法：根据不同图形的路径绘制图形
    public draw(ctx:CanvasRenderingContext2D, path:number[]){}
    // 判断点(x, y)是否在此图形内，主要用于事件点击的处理
    public pointIn(x:number, y:number):boolean{
        let shape = this.shape
        let s:Style
        let parents:Box[] = []
        let parent = this.parent
        let p:number[] = []
        while(parent){
            if(parent.name === 'stage') break
            parents.push(parent)
            parent = parent.parent
        }
        let plen = parents.length
        for(;plen--;){
            s = parents[plen].style
            p = PointIn.rotatePoint(x - s.x - s.originX, y - s.y - s.originY, 0, 0, -s.rotate * Math.PI / 180)
            x = p[0] + s.originX
            y = p[1] + s.originY
        }
        s = this.style
        p = PointIn.rotatePoint(x - s.x - s.originX, y - s.y - s.originY, 0, 0, -s.rotate * Math.PI / 180)
        x = p[0]
        y = p[1]
        // this._hit = p
        if(shape === 'circle'){
            let r = this._out[2]
            return PointIn.circle(x, y, r - s.originX, r - s.originY, r)
        }
        let radius:number[] = typeof s.radius === 'number' ? [s.radius,s.radius,s.radius,s.radius] : s.radius
        let rect:RoundRectPath = {
            x: -s.originX,
            y: -s.originY,
            width: s.width,
            height: s.height,
            radius
        }
        if(shape === 'rect'){
            return PointIn.rect(x, y, rect)
        }
        if(shape === 'roundRect'){
            return PointIn.roundRect(x, y, rect)
        }
        return false
    }
}